package com.bstek.urule.console.servlet.action;

import com.bstek.urule.config.SpringApplicationUtil;
import com.bstek.urule.console.servlet.base.BaseController;
import com.bstek.urule.model.ExposeAction;
import com.bstek.urule.model.library.Datatype;
import com.bstek.urule.model.library.action.Method;
import com.bstek.urule.model.library.action.Parameter;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.AopProxy;
import org.springframework.aop.support.AopUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.*;

/**
 * <p>
 *
 * </p>
 *
 * @author zhouchaoliang
 * @since 2021/12/8
 */
@Controller
public class ActionController extends BaseController {


    @GetMapping("/urule/actioneditor")
    public ModelAndView action(HttpServletRequest req) {
        ModelAndView mv = new ModelAndView("/action-editor");
        mv.addObject("contextPath", req.getContextPath());
        return mv;
    }

    @ResponseBody
    @PostMapping(value = "/urule/actioneditor/loadMethods")
    public List<Method> loadMethods(@RequestParam("beanId") String beanId, HttpServletResponse resp) throws ServletException, IOException {
        Object o= SpringApplicationUtil.getBean(beanId);
        Object bean=getTarget(o);
        List<Method> list=new ArrayList<Method>();
        java.lang.reflect.Method[] methods=bean.getClass().getMethods();
        for(java.lang.reflect.Method m:methods){
            ExposeAction action=m.getAnnotation(ExposeAction.class);
            if(action==null){
                continue;
            }
            String name=m.getName();
            Method method=new Method();
            method.setMethodName(name);
            method.setName(action.value());
            method.setParameters(buildParameters(m));
            list.add(method);
        }
        return list;
    }

    private Object getTarget(Object proxy){
        if(!AopUtils.isAopProxy(proxy)) {
            return proxy;//不是代理对象
        }
        try {
            if(AopUtils.isJdkDynamicProxy(proxy)) {
                return getJdkDynamicProxyTargetObject(proxy);
            } else { //cglib
                return getCglibProxyTargetObject(proxy);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
        Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
        h.setAccessible(true);
        AopProxy aopProxy = (AopProxy) h.get(proxy);
        Field advised = aopProxy.getClass().getDeclaredField("advised");
        advised.setAccessible(true);
        Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();
        return target;
    }

    private Object getCglibProxyTargetObject(Object proxy) throws Exception {
        Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
        h.setAccessible(true);
        Object dynamicAdvisedInterceptor = h.get(proxy);
        Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
        advised.setAccessible(true);

        Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();

        return target;
    }

    private List<Parameter> buildParameters(java.lang.reflect.Method m){
        List<Parameter> parameters=new ArrayList<Parameter>();
        Class<?>[] classes=m.getParameterTypes();
        for(int i=0;i<classes.length;i++){
            Class<?> c=classes[i];
            Parameter p=new Parameter();
            p.setName("参数"+i);
            p.setType(buildDatatype(c));
            parameters.add(p);
        }
        return parameters;
    }

    private Datatype buildDatatype(Class<?> clazz) {
        if(clazz.equals(String.class)){
            return Datatype.String;
        }else if(clazz.equals(BigDecimal.class)){
            return Datatype.BigDecimal;
        }else if(clazz.equals(Boolean.class)){
            return Datatype.Boolean;
        }else if(clazz.equals(boolean.class)){
            return Datatype.Boolean;
        }else if(clazz.equals(Date.class)){
            return Datatype.Date;
        }else if(clazz.equals(Double.class)){
            return Datatype.Double;
        }else if(clazz.equals(double.class)){
            return Datatype.Double;
        }else if(Enum.class.isAssignableFrom(clazz)){
            return Datatype.Enum;
        }else if(clazz.equals(Float.class)){
            return Datatype.Float;
        }else if(clazz.equals(float.class)){
            return Datatype.Float;
        }else if(clazz.equals(Integer.class)){
            return Datatype.Integer;
        }else if(clazz.equals(int.class)){
            return Datatype.Integer;
        }else if(clazz.equals(Character.class)){
            return Datatype.Char;
        }else if(clazz.equals(char.class)){
            return Datatype.Char;
        }else if(List.class.isAssignableFrom(clazz)){
            return Datatype.List;
        }else if(clazz.equals(long.class)){
            return Datatype.Long;
        }else if(clazz.equals(Long.class)){
            return Datatype.Long;
        }else if(Map.class.isAssignableFrom(clazz)){
            return Datatype.Map;
        }else if(Set.class.isAssignableFrom(clazz)){
            return Datatype.Set;
        }else{
            return Datatype.Object;
        }
    }
}
